/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.query;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.koylu.caffein.model.caffein.CaffeinConfig;
import com.koylu.caffein.model.clazz.Clazz;
import com.koylu.caffein.query.filter.And;
import com.koylu.caffein.query.filter.CompositeFilter;
import com.koylu.caffein.query.filter.Filter;
import com.koylu.caffein.query.group.Group;
import com.koylu.caffein.query.join.Join;
import com.koylu.caffein.query.order.Order;
import com.koylu.caffein.query.target.Count;
import com.koylu.caffein.query.target.Target;

@SuppressWarnings("rawtypes")
public abstract class Query {

	private Log log = LogFactory.getLog(getClass());
	private CaffeinConfig caffeinConfig;
	private Clazz clazz;
	private String clazzAlias;
	private Map<String, Clazz> aliasMap = new HashMap<String, Clazz>();
	private List<Join> joins;
	private CompositeFilter compositeFilter;
	private List<Order> orders;
	private List<Group> groups;
	private Target target;
	private int firstResult = -1;
	private int maxResult = -1;
	public static StringBuffer emptyStringBuffer = new StringBuffer();
	private static Target count = new Count("caffein_row_count");
	
	public Query() {}

	public Query(CaffeinConfig caffeinConfig, Clazz clazz, String alias) {
		this.caffeinConfig = caffeinConfig;
		this.clazz = clazz;
		clazzAlias = alias;
		target = Target.clazz(alias);
		aliasMap.put(alias, clazz);
	}
	
	public CaffeinConfig getCaffeinConfig(){
		return caffeinConfig;
	}

	public Query setFirstResult(int firstResult) {
		this.firstResult = firstResult;
		return this;
	}
	
	public int getFirstResult() {
		return firstResult;
	}

	public Query setMaxResult(int maxResult) {
		this.maxResult = maxResult;
		return this;
	}
	
	public int getMaxResult() {
		return maxResult;
	}
	
	public Query addJoin(Join join) throws Exception {
		return addJoin(join, false);
	}
	
	public Query addJoin(Join join, boolean setAsTarget) throws Exception {
		if(joins == null){
			joins = new ArrayList<Join>(5);
		}
		String alias = join.getAlias();
		aliasMap.put(alias, join.getJoinClazz(caffeinConfig, clazz));
		joins.add(join);
		if(setAsTarget){
			target = Target.clazz(alias);			
		}
		return this;
	}
	
	public Query addFilter(Filter filter) throws Exception {
		if(compositeFilter == null){
			compositeFilter = new And();
		}
		compositeFilter.addFilter(filter);	
		return this;
	}
	
	public void setCompositeFilter(CompositeFilter compositeFilter) {
		this.compositeFilter = compositeFilter;
	}
	
	public CompositeFilter getCompositeFilter() {
		return compositeFilter;
	}

	public Query addOrder(Order order) throws Exception {
		if(orders == null){
			orders = new ArrayList<Order>(5);
		}
		orders.add(order);
		return this;
	}
	
	public Query addGroupBy(String alias, String path) throws Exception {
		if(groups == null){
			groups = new ArrayList<Group>(5);
		}
		groups.add(Group.by(alias, path));
		return this;
	}
	
	public Query addGroupBy(Group group) throws Exception {
		if(groups == null){
			groups = new ArrayList<Group>(5);
		}
		groups.add(group);
		return this;
	}
	
	public Query setTarget(Target target){
		this.target = target;
		return this;
	}
	
	public Target getTarget() {
		return target;
	}
	
	public List<Join> getJoins() {
		return joins;
	}
	
	public List<Order> getOrders() {
		return orders;
	}
	
	public List<Group> getGroups() {
		return groups;
	}
	
	public Map<String, Clazz> getAliasMap() {
		return aliasMap;
	}

	public StringBuffer fromTables() throws Exception {
		StringBuffer query = new StringBuffer(" FROM ").append(clazz.getTable()).append(" ").append(clazzAlias);
		if(joins != null){
			for (Join join : joins) {
				query.append(join.toQuery(caffeinConfig, clazz, clazzAlias));
			}			
		}
		return query;
	}

	public StringBuffer whereClause() throws Exception {
		if(compositeFilter != null){
			StringBuffer query = new StringBuffer();
			if(compositeFilter.getFilters().size() > 0){
				query.append(" WHERE ").append(compositeFilter.toQuery(caffeinConfig, aliasMap));
			}
			return query;
		}
		return emptyStringBuffer;
	}

	public StringBuffer orderBy() throws Exception {
		if(orders != null){
			if (orders.size() > 0) {
				StringBuffer query = new StringBuffer(" ORDER BY ");
				for (Iterator<Order> iterator = orders.iterator(); iterator.hasNext();) {
					query.append(iterator.next().toQuery(caffeinConfig, aliasMap));
					if (iterator.hasNext()) {
						query.append(", ");
					}
				}
				return query;	
			}			
		}
		return emptyStringBuffer;
	}
	
	public StringBuffer groupBy() throws Exception {
		if(groups != null){
			if (groups.size() > 0) {
				StringBuffer query = new StringBuffer(" GROUP BY ");
				for (Iterator<Group> iterator = groups.iterator(); iterator.hasNext();) {
					query.append(iterator.next().toQuery(caffeinConfig, aliasMap));
					if (iterator.hasNext()) {
						query.append(", ");
					}
				}
				return query;
			}			
		}
		return emptyStringBuffer;
	}
	
	public StringBuffer listQuery(CaffeinConfig caffeinConfig, Map<String, Clazz> aliasMap, Target target) throws Exception {
		return new StringBuffer("SELECT ").append(target.toQuery(caffeinConfig, aliasMap)).append(fromTables()).append(whereClause()).append(groupBy()).append(orderBy());
	}
	
	public String getQuery() throws Exception{
		return new StringBuffer("SELECT ").append(target.toQuery(caffeinConfig, aliasMap)).append(fromTables()).append(whereClause()).append(groupBy()).append(orderBy()).toString();		
	}

	public abstract StringBuffer listQuery(CaffeinConfig caffeinConfig, Map<String, Clazz> aliasMap, Target target, int firstResult, int maxResult) throws Exception;
	
	public Object uniqueResult() throws Exception {
		Connection connection = null;
		try {
			connection = caffeinConfig.getConnection();
			return uniqueResult(connection);
		} catch (Exception e) {
			throw e;
		} finally {
			if (connection != null) {
				try {
					connection.close();	
				} catch (Exception e) {}
			}
		}
	}
	
	public Object uniqueResult(Connection connection) throws Exception {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			String queryString = listQuery(caffeinConfig, aliasMap, target).toString();
			if(caffeinConfig.isEnableQueryLogging()){
				log.info(queryString);
			}
			preparedStatement = connection.prepareStatement(queryString);
			preparedStatement.setMaxRows(1);
			if(compositeFilter != null){
				compositeFilter.fieldToColumn(caffeinConfig, preparedStatement, 1);				
			}
			resultSet = preparedStatement.executeQuery();
			if(resultSet.next()){
				return target.getCurrentRow(caffeinConfig, aliasMap, resultSet);				
			}
			return null;
		} catch (Exception e) {
			throw e;
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {}
			}
			if (preparedStatement != null) {
				try {
					preparedStatement.close();
				} catch (Exception ignored) {}
			}
		}
	}

	public int count() throws Exception {
		Connection connection = null;
		try {
			connection = caffeinConfig.getConnection();
			return count(connection);
		} catch (Exception e) {
			throw e;
		} finally {
			if (connection != null) {
				try {
					connection.close();	
				} catch (Exception e) {}
			}
		}
	}
	
	public int count(Connection connection) throws Exception {
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			String queryString = new StringBuffer("SELECT ").append(count.toQuery(caffeinConfig, aliasMap)).append(fromTables()).append(whereClause()).append(groupBy()).toString();
			if(caffeinConfig.isEnableQueryLogging()){
				log.info(queryString);
			}
			preparedStatement = connection.prepareStatement(queryString);
			if(compositeFilter != null){
				compositeFilter.fieldToColumn(caffeinConfig, preparedStatement, 1);				
			}
			resultSet = preparedStatement.executeQuery();
			resultSet.next();
			return Integer.parseInt(count.getCurrentRow(caffeinConfig, aliasMap, resultSet).toString());				
		} catch (Exception e) {
			throw e;
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {}					
			}
			if (preparedStatement != null) {
				try {
					preparedStatement.close();	
				} catch (Exception ignored) {}
			}
		}
	}
	
	public void prev() throws Exception{
		if(firstResult != -1 && maxResult != -1){
			if( (firstResult - maxResult) > -1 ){
				firstResult -= maxResult;
			}
		}
	}
	
	public void next() throws Exception{
		if(firstResult != -1 && maxResult != -1){
			if( (firstResult + maxResult) < count() ){
				firstResult += maxResult;
			}
		}
	}
	
	public boolean hasNext() throws Exception{
		if(firstResult != -1 && maxResult != -1){
			return (firstResult + maxResult) < count();	
		}
		return false;
	}

	public List<?> list() throws Exception {
		return (List<?>)collection(ArrayList.class);
	}
	
	@SuppressWarnings("unchecked")
	public Collection<?> collection(Class<? extends Collection> returnType) throws Exception {
		Connection connection = null;
		PreparedStatement preparedStatement = null;
		ResultSet resultSet = null;
		try {
			String queryString = null;
			if(firstResult != -1 && maxResult != -1){
				queryString = listQuery(caffeinConfig, aliasMap, target, firstResult, maxResult).toString();
			}else{
				queryString = listQuery(caffeinConfig, aliasMap, target).toString();
			}
			if(caffeinConfig.isEnableQueryLogging()){
				log.info(queryString);
			}
			connection = caffeinConfig.getConnection();
			preparedStatement = connection.prepareStatement(queryString);
			if(compositeFilter != null){
				compositeFilter.fieldToColumn(caffeinConfig, preparedStatement, 1);				
			}
			resultSet = preparedStatement.executeQuery();
			Collection<Object> result = returnType.newInstance();
			while(resultSet.next()){
				result.add(target.getCurrentRow(caffeinConfig, aliasMap, resultSet));
			}
			return result;
		} catch (Exception e) {
			throw e;
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (Exception ignored) {}
			}
			if (preparedStatement != null) {
				try {
					preparedStatement.close();
				} catch (Exception ignored) {}
			}
			if (connection != null) {
				try {
					connection.close();	
				} catch (Exception ignored) {}
			}
		}
	}
}